package com.harth.flutter_ns_ble

import android.content.Context
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.async
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.flow.timeout
import kotlinx.coroutines.withContext
import no.nordicsemi.android.ble.data.Data
import java.io.File
import java.io.RandomAccessFile
import java.nio.ByteBuffer
import java.nio.ByteOrder
import kotlin.time.Duration.Companion.seconds

/**
 * 同步动态设备数据
* */
suspend fun PeripheralManager.syncData(
    context: Context,
    address: String, peripheral: PeripheralManager.Peripheral, filePath: String?,
    deleteBeforeWrite: Boolean?,
) = withContext(Dispatchers.IO) {

    val frameSize = 244
    suspend fun startSyncData(index: Int) {
        val data = ByteBuffer.allocate(4)
            .order(ByteOrder.LITTLE_ENDIAN)
            .putInt(index)
            .array()
            .copyOfRange(0, 3)
        peripheral.write(CMD.MEAS_FLASH, data = data)
    }
    callSink(
        B1SyncDataEvent(
            address = address,
            state = B1SyncDataState.START
        )
    )
    val file = if (filePath != null) {
        File(filePath)
    } else {
        val event = peripheral.request(CMD.TIME_CACHE, byteArrayOf(0))
        val time = event.data?.let { data ->
            if (data.contentEquals(
                    byteArrayOf(
                        0xff.toByte(),
                        0xff.toByte(),
                        0xff.toByte(),
                        0xff.toByte()
                    )
                )
            ) {
                0
            } else {
                Data(data).getIntValue(Data.FORMAT_SINT32_LE, 0)!! * 1000
            }
        } ?: 0
        File("${context.cacheDir.path}/${time}.raw")
    }

    if (deleteBeforeWrite == true){
        if (file.exists()){
            file.delete()
        }
    }

    val flashCountResp = peripheral.request(CMD.MEAS_FLASH_COUNT, null)
    val flashCount =
        flashCountResp.data?.let { Data(it).getIntValue(Data.FORMAT_SINT32_LE, 0) }
            ?: 0

    RandomAccessFile(file, "rw").use { randomAccessFile ->
        val fileLength = randomAccessFile.length()
        var index = (fileLength / frameSize).toInt()

        val asyncJob = async {
            channelFlow<PeripheralNotifyEvent> {
                try {
                    notifyFlow
                        .timeout(2.seconds)
                        .collect {
                            if (it.cmd == CMD.MEAS_COUNT) {
                                close()
                            } else if (it.cmd == CMD.MEAS_FLASH) {
                                send(it)
                            }
                        }
                } catch (e: TimeoutCancellationException) {
                    e.printStackTrace()
                    if (index + 1 == flashCount) {
                        close()
                    } else {
                        throw e
                    }
                }

            }.collect {
                if (it.cmd == CMD.MEAS_FLASH) {
                    it.data?.let { data ->
                        val receivedIndex =
                            Data(
                                data.copyOfRange(
                                    0,
                                    3
                                ) + byteArrayOf(0)
                            ).getIntValue(
                                Data.FORMAT_SINT32_LE,
                                0
                            ) ?: -1
                        if (receivedIndex == index) {
                            randomAccessFile.seek(frameSize * index.toLong())
                            randomAccessFile.write(it.raw)
                            callSink(
                                B1SyncDataEvent(
                                    address = address,
                                    state = B1SyncDataState.PROGRESS,
                                    receivedSize = index.toLong(),
                                    totalSize = flashCount.toLong()
                                )
                            )
                            index += 1
                        } else {
                            peripheral.write(CMD.STOP_SYNC)
                            startSyncData(index)
                        }
                    }
                }
            }
        }


        startSyncData(index)
        asyncJob.await()
        callSink(
            B1SyncDataEvent(
                address = address,
                state = B1SyncDataState.PROGRESS,
                receivedSize = flashCount.toLong(),
                totalSize = flashCount.toLong()
            )
        )
    }

    callSink(
        B1SyncDataEvent(
            address = address,
            state = B1SyncDataState.COMPLETED
        )
    )
}